import { IEntity, IMessage, IPerson, ISession, ITarget, MessageType } from '../..';
import { Command, common, model, schema } from '../../../base';
import { Entity } from '../../public';
import { XCollection } from '../../public/collection';
import { Message } from '../message';
import { BodyContent, IModel, TokenStatus } from './aiModel';

export interface IAgentManager extends common.Emitter {
  /** 所在组织 */
  target: ITarget;
  /** 对话用户 */
  user: IPerson;
  /** 创建的智能体 */
  agents: IAgent[];
  /** 当前对话模型 */
  curAgent: IAgent | undefined;
  /** 当前使用模型 */
  curModel: IModel | undefined;
  /** 触发器 */
  command: Command;
  /** 创建智能体 */
  createAgent(agent: schema.XAgent, send?: boolean): Promise<schema.XAgent | undefined>;
  /** 设置当前智能体 */
  setCurAgent(agent: IAgent): Promise<boolean>;
  /** 加载当前智能体 */
  loadCurAgent(): Promise<IAgent | undefined>;
  /** 设置当前模型 */
  setCurModel(model: IModel): Promise<boolean>;
  /** 加载当前模型 */
  loadCurModel(): Promise<IModel | undefined>;
  /** 加载智能体 */
  loadContent(reload?: boolean): Promise<IAgent[]>;
}

export class AgentManager extends common.Emitter implements IAgentManager {
  constructor(target: ITarget) {
    super();
    this.key = common.generateUuid();
    this.target = target;
    this.user = target.user;
    this.command = new Command();
    this._subscribe();
  }

  key: string;
  target: ITarget;
  user: IPerson;
  agents: IAgent[] = [];
  command: Command;

  curAgent: IAgent | undefined;
  curModel: IModel | undefined;

  loaded: boolean = false;

  get keyword() {
    return this.target.relations.join('-');
  }

  async setCurModel(model: IModel): Promise<boolean> {
    this.curModel = model;
    return await this.user.cacheObj.set(this.keyword + '-model', model.id);
  }

  async setCurAgent(agent: IAgent): Promise<boolean> {
    this.curAgent = agent;
    return await this.user.cacheObj.set(this.keyword + '-agent', agent.id);
  }

  async createAgent(
    agent: schema.XAgent,
    send?: boolean,
  ): Promise<schema.XAgent | undefined> {
    agent.typeName = '智能体';
    agent.targetId = this.target.id;
    const result = await this.user.resource.agentColl.insert(agent);
    if (result) {
      await this.user.resource.agentColl.notity({
        operate: 'create',
        data: result,
        send,
      });
    }
    return result;
  }

  async loadContent(reload?: boolean): Promise<IAgent[]> {
    if (reload || !this.loaded) {
      const agents = await this.user.resource.agentColl.loadSpace({
        options: { match: { targetId: this.target.id } },
      });
      this.agents = agents.map((item) => new Agent(item, this.target));
      this.loaded = true;
    }
    return this.agents;
  }

  async loadCurAgent(): Promise<IAgent | undefined> {
    await this.loadContent();
    if (!this.curAgent) {
      const agent = await this.user.cacheObj.get<string>(this.keyword + '-agent');
      if (agent) {
        this.curAgent = this.agents.find((item) => item.id === agent);
      }
    }
    return this.curAgent;
  }

  async loadCurModel(): Promise<IModel | undefined> {
    await this.target.modelManager.loadContent();
    if (!this.curModel) {
      const model = await this.user.cacheObj.get<string>(this.keyword + '-model');
      if (model) {
        this.curModel = this.target.modelManager.models.find((item) => item.id === model);
      }
    }
    return this.curModel;
  }

  private _subscribe() {
    this.user.resource.agentColl.subscribe([this.key], async (data) => {
      switch (data.operate) {
        case 'create':
          if (data.data.targetId === this.target.id) {
            const agent = new Agent(data.data, this.target, data.send);
            this.agents.push(agent);
            await this.setCurAgent(agent);
            this.command.emitter('agent', 'create', agent);
          }
          break;
      }
    });
  }
}

interface SendParams {
  type: MessageType;
  content: string;
}

export interface StreamResponse {
  status: TokenStatus;
  message: model.ChatMessageType;
  content: BodyContent;
}

export interface IUpdatableMessage extends IMessage {
  /** 更新 */
  update(data: model.ChatMessageType): Promise<model.ChatMessageType | undefined>;
}

export class UpdatableMessage extends Message implements IUpdatableMessage {
  constructor(metadata: model.ChatMessageType, chat: ISession, agent: IAgent) {
    super(metadata, chat);
    this.agent = agent;
  }
  agent: IAgent;

  async update(metadata: model.ChatMessageType) {
    const data = await this.agent.coll.replace(metadata);
    if (data) {
      await this.agent.coll.notity({ operate: 'update', data });
    }
    return data;
  }
}

interface GenTokensParams {
  content: string;
  message: model.ChatMessageType;
  onStart?: () => Promise<void>;
  onEvent: (data: StreamResponse) => Promise<void>;
}

export interface IAgent extends IEntity<schema.XAgent> {
  /** 状态 */
  status: TokenStatus;
  /** 临时消息 */
  content: string;
  /** 消息 */
  messages: IUpdatableMessage[];
  /** 消息 */
  coll: XCollection<model.ChatMessageType>;
  /** 管理器 */
  manager: IAgentManager;
  /** 组织 */
  target: ITarget;
  /** 触发器 */
  command: Command;
  /** 终止器 */
  aborter?: AbortController;
  /** 初始化任务 */
  task?: boolean;
  /** 状态变更订阅 */
  onStatusChanged?: (status: TokenStatus) => Promise<void>;
  /** 加載消息 */
  loadMores(): Promise<IUpdatableMessage[]>;
  /** 监听消息 */
  onMessage(on: (messages: IUpdatableMessage[]) => void): Promise<void>;
  /** 取消监听 */
  unMessage(): void;
  /** 发送消息 */
  sendMessage(params: SendParams): Promise<model.ChatMessageType | undefined>;
  /** 生成智能体消息 */
  backMessage(): Promise<model.ChatMessageType | undefined>;
  /** 生成回复 */
  genModelTokens(model: IModel, params: GenTokensParams): Promise<void>;
}

export class Agent extends Entity<schema.XAgent> implements IAgent {
  constructor(agent: schema.XAgent, target: ITarget, task?: boolean) {
    super(agent, []);
    this.key = common.generateUuid();
    this.user = target.user;
    this.target = target;
    this._subscribe();
    this.command = new Command();
    this.task = task;
  }
  command: Command;
  status: TokenStatus = TokenStatus.aborted;
  task?: boolean;
  onStatusChanged?: (status: TokenStatus) => Promise<void>;

  aborter?: AbortController;
  content: string = '';
  key: string;
  user: IPerson;
  target: ITarget;
  messages: IUpdatableMessage[] = [];
  messageNotify?: (messages: IUpdatableMessage[]) => void;

  get coll() {
    return this.user.resource.tokenColl;
  }

  get manager() {
    return this.target.agentManager;
  }

  private _subscribe() {
    this.coll.subscribe([this.key], (data) => {
      switch (data.operate) {
        case 'insert':
          const item = data.data as model.ChatMessageType;
          if (
            [item.fromId, item.toId].includes(this.id) &&
            [item.fromId, item.toId].includes(this.userId)
          ) {
            this.messages.push(new UpdatableMessage(item, this.user.session, this));
            this.messageNotify?.(this.messages);
          }
          break;
        case 'update':
          for (let index = 0; index < this.messages.length; index++) {
            const message = this.messages[index];
            if (message.id == data.data.id) {
              this.messages[index] = new UpdatableMessage(
                data.data as model.ChatMessageType,
                this.user.session,
                this,
              );
              this.messageNotify?.(this.messages);
              break;
            }
          }
          break;
      }
    });
  }

  async loadMores(): Promise<IUpdatableMessage[]> {
    const data = await this.coll.loadSpace({
      take: 10,
      skip: this.messages.length,
      options: {
        match: {
          _or_: [
            { fromId: this.id, toId: this.userId },
            { fromId: this.userId, toId: this.id },
          ],
          isDeleted: false,
        },
        sort: {
          createTime: -1,
        },
      },
    });
    if (data && data.length > 0) {
      data.forEach((item) =>
        this.messages.unshift(new UpdatableMessage(item, this.user.session, this)),
      );
    }
    return this.messages;
  }

  async onMessage(on: (messages: IUpdatableMessage[]) => void) {
    this.messageNotify = on;
    await this.loadMores();
    this.messageNotify?.(this.messages);
  }

  unMessage(): void {
    this.messageNotify = undefined;
  }

  async sendMessage(params: SendParams): Promise<model.ChatMessageType | undefined> {
    const data = await this.coll.insert({
      typeName: params.type,
      fromId: this.userId,
      toId: this.id,
      comments: [],
      designateId: this.userId,
      content: common.StringPako.deflate(
        '[obj]' + JSON.stringify({ body: params.content }),
      ),
    } as unknown as model.ChatMessageType);
    if (data) {
      await this.coll.notity({ operate: 'insert', data });
    }
    return data;
  }

  async backMessage(): Promise<model.ChatMessageType | undefined> {
    const data = await this.coll.insert({
      typeName: MessageType.Tokens,
      fromId: this.id,
      toId: this.userId,
      comments: [],
      designateId: this.userId,
      content: common.StringPako.deflate(
        '[obj]' +
          JSON.stringify({
            body: {
              content: '',
              reasoning: '',
            },
          }),
      ),
    } as unknown as model.ChatMessageType);
    if (data) {
      await this.coll.notity({ operate: 'insert', data });
    }
    return data;
  }

  async genModelTokens(model: IModel, params: GenTokensParams): Promise<void> {
    let updatable = new UpdatableMessage(params.message, this.user.session, this);

    let updating = async (status: TokenStatus, body: any) => {
      this.status = status;
      await this.onStatusChanged?.(status);
      await updatable.update({
        ...updatable.metadata,
        status,
        content: common.StringPako.deflate('[obj]' + JSON.stringify({ body })),
      });
      params.onEvent({ status, message: params.message, content: body });
    };

    await model.genTokens({
      content: params.content,
      message: params.message,
      abortCtrl: this.aborter,
      onStart: async () => {
        await params.onStart?.();
      },
      onEvent: async (status, body) => {
        await updating(status, body);
      },
    });
  }
}
